package com.gopay.bis.common.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;

import javax.xml.crypto.dsig.TransformException;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;

import com.gopay.bis.common.util.constants.Constants4BankSwitch;

/**
 * TransUtil.java
 * <p>
 * Created on Oct 30, 2007 Modification history
 * <p>
 * Bankswitch模块的工具类.
 * 
 * @author Richard Chen
 * @version 1.0
 * @since 1.0
 */

public abstract class TransUtil {

    /**
     * No instantiation.
     */
    protected TransUtil() {
        throw new UnsupportedOperationException();
    }

    /**
     * Fetch designated field value in the message body according to the
     * appointed tag pair. If there are more than one open tag or close tag,
     * then return the string value between first open tag and first close tag;
     * or throw <code>TransformException</code>.
     * 
     * @param beginTag
     *            起始标签.
     * @param endTag
     *            结束标签.
     * @param msg
     *            Message body as <code>String</code>.
     * @return String 域值.
     * @throws TransformException
     *             缺少指定的起始或结束标签.
     */
    public static String getMsgField(String beginTag, String endTag, String msg) {
        int beginIndex = msg.indexOf(beginTag);
        int endIndex = msg.indexOf(endTag);
        if (beginIndex == -1 || endIndex == -1) {
            throw new RuntimeException("Unintegrated message, with absence of " + beginTag);
        }
        String returnCode = msg.substring(beginIndex + beginTag.length(), endIndex);
        return returnCode;
    }

    /**
     * Get date string in a yyyyMMddhhmmssSSS style, like 20071126091234111
     * means Nov.26, 2007 09:12:34:111.
     * 
     * @return date string in a yyyyMMddhhmmssSSS style.
     */
    public static String getSendTime() {
        return getTimeString(TIME_FORMAT_YEAR_MILLISECOND);
    }

    /**
     * Get date string in a yyyyMMddHHmmss style, like 20071126091234 means
     * Nov.26, 2007 09:12:34.
     * 
     * @return date string in a yyyyMMddHHmmss style.
     */
    public static String getTransTime() {
        return getTimeString(TIME_FORMAT_YEAR_SECOND);
    }

    /**
     * Get date for bank CITIC.
     * 
     * @return .
     */
    public static String getDateForCITIC() {
        return getTimeString(TIME_FORMAT_YEAR_Y);
    }

    /**
     * 生成yyMMddhhmmssSSS格式的时间字符串，工行报文PackageID字段用.
     * 
     * @return yyMMddhhmmssSSS格式的时间字符串.
     */
    public static String getPackageID() {
        StringBuffer stringBuffer = new StringBuffer(getTimeString(TIME_FORMAT_TWO_BIT_YEAR_SECOND));
        int tmp = 0;
        synchronized (LOCK) {
            suffix %= LENGTH;
            tmp = suffix;
            suffix++;
        }
        if (tmp < TWO_BIT) {
            stringBuffer.append(SUFFIX_TWO_ZERO).append(tmp);
        } else if (tmp < THREE_BIT) {
            stringBuffer.append(SUFFIX_ONE_ZERO).append(tmp);
        } else {
            stringBuffer.append(tmp);
        }
        return stringBuffer.toString();
    }

    /**
     * 生成yyyy-MM-dd HH:mm:ss格式的时间字符串，民生报文dtClient字段用.
     * 
     * @return yyyy-MM-dd HH:mm:ss格式的时间字符串.
     */
    public static String getDtClient() {
        return getTimeString(TIME_FORMAT_YEAR_SECOND_G);
    }

    /**
     * 生成yyyy-MM-dd格式的时间字符串，民生报文时间字段用.
     * 
     * @return yyyy-MM-dd格式的时间字符串.
     */
    public static String getCMBCDate() {
        return getTimeString(TIME_FORMAT_YEAR_G);
    }

    /**
     * 取得时间字符串.
     * 
     * @param timeFormat
     *            指定时间字符串的格式, 参考{@link java.text.SimpleDateFormat}.
     * @return 以timeFormat格式返回时间.
     */
    private static String getTimeString(String timeFormat) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(timeFormat);
        String sendTime = simpleDateFormat.format(new Date());
        return sendTime;
    }

    /**
     * 对一金额字符串进行去小数点操作, 等于对输入金额放大一百倍, 返回金额字符串不能包括小数点.
     * 如amountConvert2Fen("100.5")则返回"10050".
     * 
     * @param amountStr
     *            必须遵循正则表达式<code>^(\\d{1,10}(\\.\\d{0,2})?)$</code>, 否则抛出
     *            <code>IllegalArgumentException</code>异常.
     * @return 返回金额字符串为原金额的一百倍.
     */
    public static String amountConvert2Fen(String amountStr) {

        if (!PATTERN.matcher(amountStr).matches()) {
            throw new IllegalArgumentException(amountStr + "输入的金额格式有误，无法转化金额为分");
        }
        BigDecimal originalAmount = new BigDecimal(amountStr);
        String resultStr = originalAmount.multiply(ONEHUNDRED).setScale(0).toString();
        return resultStr;
    }

    /**
     * 生成yyyyMMddhhmmssSSS格式时间字符串，工行报文SignTime字段用.
     * 
     * @return yyyyMMddhhmmssSSS格式的时间字符串.
     */
    public static String getSignTime() {
        return getTimeString(TIME_FORMAT_YEAR_MILLISECOND);
    }

    /**
     * Replace the reserve keyword of XML from <code>originalStr</code> with XML
     * entity.
     * <ul>
     * Refer to XML Spec.
     * </ul>
     * <li>[<] --> &lt; <li>[>] --> &gt; <li>[&] --> &amp; <li>['] --> &apos;
     * <li>["] --> &quot;
     * 
     * @param originalStr
     *            未处理的XML报文.
     * @return processed string with all reserve keyword replaced.
     */
    public static String doXmlEntityEscape(String originalStr) {
        String resultStr = originalStr
                .replaceAll(Constants4BankSwitch.XML_RESERVE_AMP, Constants4BankSwitch.XML_ENTITY_AMP)
                .replaceAll(Constants4BankSwitch.XML_RESERVE_LT, Constants4BankSwitch.XML_ENTITY_LT)
                .replaceAll(Constants4BankSwitch.XML_RESERVE_GT, Constants4BankSwitch.XML_ENTITY_GT)
                .replaceAll(Constants4BankSwitch.XML_RESERVE_APOS, Constants4BankSwitch.XML_ENTITY_APOS)
                .replaceAll(Constants4BankSwitch.XML_RESERVE_QUOT, Constants4BankSwitch.XML_ENTITY_QUOT);
        return resultStr;
    }

    /**
     * Remove the CDATA segement of the string <code>originalStr</code>.
     * 
     * @param originalStr
     *            未处理的XML报文.
     * @return string with CDATA removed.
     */
    public static String doFilter(String originalStr) {
        String resultStr1 = originalStr.replaceAll(Constants4BankSwitch.XML_CDATA_HEADER,
                Constants4BankSwitch.XML_CDATA_PLACEHOLDER);
        String resultStr = resultStr1.replaceAll(Constants4BankSwitch.XML_CDATA_TAIL,
                Constants4BankSwitch.XML_CDATA_PLACEHOLDER);
        return resultStr;
    }

    /**
     * 民生银行读取证书文件.
     * 
     * @param filename
     *            文件名.
     * @return 证书文件二进制流.
     * @throws Exception
     *             读取出错.
     */
    public static byte[] readCertFileForCMBCB2C(String filename) throws Exception {
        try {
            FileInputStream fileInStream = new FileInputStream(filename);
            ByteArrayOutputStream fileByteStream = new ByteArrayOutputStream();
            int i = 0;
            while ((i = fileInStream.read()) != -1) {
                fileByteStream.write(i);
            }
            fileInStream.close();
            return fileByteStream.toByteArray();
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }

    /**
     * 按指定的格式将<code>java.util.Date</code>转换为字符串.
     * 
     * @param dateFormat
     *            指定日期字符串的格式, 参考{@link java.text.SimpleDateFormat}.
     * @param date
     *            需转换格式的日期.
     * @return 指定格式的日期字符串.
     */
    public static String getDateStr(String dateFormat, Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
        String dateStr = simpleDateFormat.format(date);
        return dateStr;
    }

    /**
     * 按yyyyMMdd格式取当前日期的字符串.
     * 
     * @return 当前日期的字符串.
     */
    public static String getCurrentDateStr() {
        return getDateStr(CURRENT_DATE, new Date());
    }

    /**
     * 将62以下数字转换成个位数+字母.
     * 
     * @param numStr
     * @return 转化后数据
     */
    public static String convertU62Num(String numStr) {
        int num = 0;
        try {
            num = Integer.parseInt(numStr);
        } catch (NumberFormatException ex) {
            throw new IllegalArgumentException("parseInt失败，传入的" + numStr + "非法");
        }
        if (num > CONVERT_NUM_ARR.length) {
            throw new IllegalArgumentException("传入的参数不可大于62");
        }
        return CONVERT_NUM_ARR[num];
    }

    /**
     * Get appointed file content according to the <code>filePath</code>. This
     * method will try read the file in following sequence:
     * <ul>
     * <li>1. From classpath.
     * <li>2. From file system, if 1 failed.
     * </ul>
     * So, <code>filePath</code> can be one of following forms:
     * <ul>
     * <li>1. Prefixed with <code>classpath:</code> indicate a classpath
     * resource.
     * <li>2. Prefixed with <code>file:</code> indicate an absolute file path
     * resource.
     * <li>3. Plain paths will always be interpreted as relative to the current
     * VM working directory, even if they start with a slash.
     * </ul>
     * 
     * @param filePath
     *            the path of the file.
     * @return file content.
     * @throws FileNotFoundException
     *             if appointed file can not be read or not exist.
     */
    public static byte[] getFile(String filePath) throws FileNotFoundException {
        try {
            ResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(filePath);
            InputStream inputStream = null;
            if (resource.exists()) {
                inputStream = resource.getInputStream();
            } else {
                resourceLoader = new FileSystemResourceLoader();
                resource = resourceLoader.getResource(filePath);
                if (resource.exists()) {
                    inputStream = resource.getInputStream();
                } else {
                    inputStream = new FileInputStream(filePath);
                }
            }
            byte[] fileByte = new byte[inputStream.available()];
            int fileLength = inputStream.read(fileByte);
            inputStream.close();
            if (fileLength >= 0) {
                byte[] targetFile = new byte[fileLength];
                System.arraycopy(fileByte, 0, targetFile, 0, fileLength);
                return targetFile;
            } else {
                throw new FileNotFoundException("File: [" + filePath + "] can not be read or is empty");
            }
        } catch (Exception e) {
            if (e instanceof FileNotFoundException) {
                throw (FileNotFoundException) e;
            }
            throw new FileNotFoundException("File: [" + filePath + "] can not be read");
        }
    }

    /**
     * Get the canonical file path according to the relative or classpath file
     * path.
     * 
     * @param filePath
     *            relative or classpath file path.
     * @return canonical file path, or return <code>filePath</code> if
     *         designated file does not exist or can not be read due to
     *         permission.
     */
    public static String getCanonicalFilePath(String filePath) {
        try {
            ResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(filePath);
            File file = null;
            if (resource.exists()) {
                file = resource.getFile();
            } else {
                resourceLoader = new FileSystemResourceLoader();
                resource = resourceLoader.getResource(filePath);
                if (resource.exists()) {
                    file = resource.getFile();
                } else {
                    return filePath;
                }
            }
            if (file != null && file.exists() && file.isFile()) {
                String absFilePath = file.getCanonicalPath();
                return absFilePath;
            } else {
                return filePath;
            }
        } catch (Exception e) {
            return filePath;
        }
    }

    /**
     * Get the canonical file path according to the relative or classpath file
     * path.
     * 
     * @param filePath
     *            relative or classpath file path.
     * @param ignoreFileExist
     *            <code>true</code> indicate whether file exists return
     *            canonical path of the file.
     * @return canonical file path, or return <code>filePath</code> if
     *         designated file does not exist or can not be read due to
     *         permission.
     */
    public static String getCanonicalFilePath(String filePath, boolean ignoreFileExist) {
        if (!StringUtils.hasLength(filePath)) {
            throw new IllegalArgumentException("filePath can not be null or empty");
        }
        if (!ignoreFileExist) {
            String cannonicalFilePath = TransUtil.getCanonicalFilePath(filePath);
            return cannonicalFilePath;
        }
        final int forwardSlash = 0x2F;
        final int backSlash = 0x5C;
        final int begin = 0;
        final int step = 1;
        String directory = null;
        if (filePath.lastIndexOf(forwardSlash) != -1) {
            directory = filePath.substring(begin, filePath.lastIndexOf(forwardSlash) + step);
        } else if (filePath.lastIndexOf(backSlash) != -1) {
            directory = filePath.substring(0, filePath.lastIndexOf(backSlash) + step);
        } else {
            directory = filePath;
        }
        Resource resource = new ClassPathResource(directory);
        try {
            File file = resource.getFile();
            StringBuffer path = new StringBuffer(file.getCanonicalPath());
            if (directory.length() != filePath.length()) {
                path.append(File.separator).append(filePath.substring(directory.length()));
            }
            String pathStr = path.toString();
            return pathStr;
        } catch (IOException e) {
            return filePath;
        }
    }

    /**
     * 为银行定制12位订单号.(国付宝默认16位)
     * 
     * @param orderId
     * @return String
     * @throws Exception
     */
    public static String processOrderIdTo12Digit(String orderId) {
        // 订单号（规定12位，要进行处理）
        // 分别截取年月日
        String yearStr = orderId.substring(2, 4);
        String monthStr = orderId.substring(4, 6);
        String dayStr = orderId.substring(6, 8);
        // 截取订单号
        String cebOrderId = yearStr + convertU62Num(monthStr) + convertU62Num(dayStr) + orderId.substring(8);

        return cebOrderId;
    }

    /**
     * 为银行定制15位订单号.(国付宝默认16位)
     * 
     * @param orderId
     * @return String
     * @throws Exception
     */
    public static String processOrderIdTo15Digit(String orderId) {
        // 订单号（规定15位，要进行处理）
        // 截取月
        String monthStr = orderId.substring(4, 6);

        // 截取订单号
        orderId = orderId.substring(0, 4) + convertU62Num(monthStr) + orderId.substring(6);

        return orderId;
    }

    /**
     * 为银行定制14位订单号.(国付宝默认16位)
     * 
     * @param orderId
     * @return String
     * @throws Exception
     */
    public static String processOrderIdTo14Digit(String orderId) {
        // 订单号（规定14位，要进行处理）
        // 将年的前两位截掉

        orderId = orderId.substring(2);

        return orderId;
    }

    /**
     * 为银行定制8位订单号.(国付宝默认16位)
     * 
     * @param orderId
     * @return String
     * @throws Exception
     */
    public static String processOrderIdTo8Digit(String orderId) {
        // 订单号（规定8位，要进行处理）
        // 截取订单号（将年月日信息截掉只使用系统生成的NextID）
        orderId = orderId.substring(8);
        return orderId;
    }

    /**
     * 为银行定制10位订单号.(国付宝默认16位)
     * 
     * @param orderId
     * @return String
     * @throws Exception
     */
    public static String processOrderIdTo10Digit(String orderId) {
        // 订单号（规定10位，要进行处理）
        // 分别截取年月
        String yearStr = orderId.substring(2, 4);
        String monthStr = orderId.substring(4, 6);
        // 截取订单号
        String cebOrderId = convertU62Num(yearStr) + convertU62Num(monthStr) + orderId.substring(8);

        return cebOrderId;
    }

    /**
     * 判断字符串是否有值.
     * 
     * @param str
     * @return
     */
    public static boolean isStringBlank(String str) {
        return (str == null || "".equals(str));
    }

    /** 金额表达式. */
    private static final String AMOUNT_REGULAR_EX = "^(\\d{1,10}(\\.\\d{0,2})?)$";
    /** 金额模式. */
    private static final Pattern PATTERN = Pattern.compile(AMOUNT_REGULAR_EX);
    /** 100, 工行金额放大一百倍. */
    private static final BigDecimal ONEHUNDRED = new BigDecimal(100);
    /** Integer 0. */
    public static final Integer LOCK = new Integer(0);
    /** 两位. */
    private static final int TWO_BIT = 10;
    /** 三位. */
    private static final int THREE_BIT = 100;
    /** 长度. */
    private static final int LENGTH = 1000;
    /** Suffix. */
    private static int suffix = 0;
    /** 插一个零. */
    private static final char SUFFIX_ONE_ZERO = '0';
    /** 插两个零. */
    private static final String SUFFIX_TWO_ZERO = "00";
    /** 时间格式yyyyMMddHHmmss. */
    private static final String TIME_FORMAT_YEAR_SECOND = "yyyyMMddHHmmss";
    /** 时间格式yyyyMMddHHmmssSSS. */
    private static final String TIME_FORMAT_YEAR_MILLISECOND = "yyyyMMddHHmmssSSS";
    /** 时间格式yyyyMMddHHmmSSS. */
    // private static final String TIME_FORMAT_YEAR_SECOND_F =
    // "yyyyMMddHHmmSSS";
    /** 时间格式yyMMddHHmmSSS. */
    private static final String TIME_FORMAT_TWO_BIT_YEAR_SECOND = "yyMMddHHmmss";
    /** 时间格式yyyy-MM-dd HH:mm:ss. */
    private static final String TIME_FORMAT_YEAR_SECOND_G = "yyyy-MM-dd HH:mm:ss";
    /** 时间格式yyyy-MM-dd. */
    private static final String TIME_FORMAT_YEAR_G = "yyyy-MM-dd";
    /** 时间格式yyyyMMdd. */
    private static final String TIME_FORMAT_YEAR_Y = "yyyyMMdd";
    /** 时间格式yyyyMMdd. */
    private static final String CURRENT_DATE = "yyyyMMdd";
    /**
     * 用于格式化62以下数字-->个位数字+字母
     */
    private static final String[] CONVERT_NUM_ARR = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
            "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
            "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };

    public static void main(String[] args) {
        System.out.println(processOrderIdTo14Digit("1234567890123456"));
    }
}
